package io.gitee.thant.utils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogHelper {
	//private StackTraceElement begin;
	private List<String[]> infolst; //数组长度固定为2，0：日志类型 1：日志信息
	private Consumer<LogHelper> endfunc; //结束处理函数
	private int endfuncno = 0; //结束处理函数的优先级，值越小优先级越高，默认为0=不可覆盖
	//private Object writer;
	//private Map<String, Method> methods;
	//private Object object;
	//private Object memo;
	private Exception lastError;
	private long time;
	private String stime;
	private Object ret;
	
	private LogHelper(/*Object object, Object loger, */Consumer<LogHelper> callback, int no) {
		SimpleDateFormat dtfmt =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		time = System.currentTimeMillis();
		stime = dtfmt.format(time);
		infolst = new ArrayList<String[]>();
		//begin = Thread.currentThread().getStackTrace()[3];
		endfunc = callback;
		endfuncno = no;
		//writer = loger;
		//methods = new HashMap<String, Method>();
		//object = (null == object) ? StringUtil.getUUID() : object;
		//memo = StringUtil.stringify(object, null);
	}
	
	private static final Logger logger = LoggerFactory.getLogger(LogHelper.class);
	private static final ThreadLocal<List<LogHelper>> _log = new ThreadLocal<List<LogHelper>>();
	
	private static String buildMessage(String msg) {
		StackTraceElement[] stackA = Thread.currentThread().getStackTrace();
		int deep = 3;
		while(deep<stackA.length) {
			if (!stackA[deep].getClassName().equals(LogHelper.class.getName())) {
				break;
			}
			++deep;
		}
		return String.format("%s %s\n", stackA[deep].toString(), msg);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T begin(String title
			,Runnable processfunc, Consumer<Exception> expfunc, Consumer<LogHelper> endfunc, int funcno) {
		/*if (StringUtil.isEmpty(title)) {
			title = Thread.currentThread().getStackTrace()[2].getMethodName();
		}*/
		
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst) {
			logerlst = new ArrayList<LogHelper>();
			_log.set(logerlst);
		}

		LogHelper loger = new LogHelper(endfunc, funcno);
		loger.infolst.add(new String[]{"info", buildMessage((StringUtil.isEmpty(title) ? "" : title+" ") 
			+"begin at "+loger.stime+"["+loger.time+"]")});
		logerlst.add(loger);
		
		T ret = null;
		if (processfunc != null) {
			try {
				processfunc.run();
				ret = (T)loger.getRet();
			} catch (Exception e) {
				if (null == expfunc) {
					error(e);
				} else {
					expfunc.accept(e);
				}
			} finally {
				end();
			}
		}
		return ret;
	}

	public static void begin(String title, Consumer<LogHelper> endfunc) {
		begin(title, null, null, endfunc, 0);
	}
	
	public static void currentMethodInfo(Object ...argA) {
		StackTraceElement[] stackA = Thread.currentThread().getStackTrace();
		String currentMtdName = stackA[2].getMethodName();
		/* 取参数名的版本 如果类存在名称且参数数量都相同的多个方法，可能会出错
		 * 且要求1.8及以上class版本
		 * 保留参数名这一选项由编译开关javac -parameters打开，默认是关闭的
		 * eclipse中可设置选项Store information about method parameters(usable via reflection)
		 * 否则取到的参数名是arg0--argN 
		Method[] mtdA;
		try {
			mtdA = Class.forName(stackA[2].getClassName()).getDeclaredMethods();
		} catch (Exception e) {
			error(e);
			return;
		}
		for (int i=0; i<mtdA.length; ++i) {
			if (currentMtdName.equals(mtdA[i].getName()) &&
				argA.length == mtdA[i].getParameterCount()) {
				Parameter[] parmA = mtdA[i].getParameters();

				StringBuilder sb = new StringBuilder("Call ");
				sb.append(currentMtdName).append('(');
				for (int j=0; j<parmA.length; ++j) {
					if (j>0) sb.append(',');
					sb.append(parmA[j].getName()).append('=');
					StringUtil.stringify(argA[j], sb);
				}
				sb.append(')');
				
				info(sb.toString());
				break;
			}
		}*/
		StringBuilder sb = new StringBuilder("Call ");
		sb.append(currentMtdName).append('(');
		for (int j=0; j<argA.length; ++j) {
			if (j>0) sb.append(',');
			StringUtil.stringify(argA[j], sb);
		}
		sb.append(')');
		info(sb.toString());
	}
	
	private static void log(String level, String msg) {
		String fullmsg = buildMessage(msg);
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) {
			switch(level) {
			case "trace": logger.trace(fullmsg); break;
			case "debug": logger.debug(fullmsg); break;
			case "info":  logger.info(fullmsg);  break;
			case "warn":  logger.warn(fullmsg);  break;
			case "error": logger.error(fullmsg); break;
			}
			return;
		}
		LogHelper loger = logerlst.get(logerlst.size()-1);

		//日志记录
		loger.infolst.add(new String[]{level, fullmsg});

		/*if (loger.writer != null) {
			try {
				Method mtd = loger.methods.get(level);
				if (null == mtd) {
					Class<?> cls;
					if (loger.writer instanceof String) {
						cls = Class.forName((String)loger.writer);
					} else {
						cls = loger.writer.getClass();
					}
					mtd = cls.getMethod(level, String.class);
					loger.methods.put(level, mtd);
				}
				mtd.invoke((loger.writer instanceof String) ? null : loger.writer, fullmsg);
			} catch (Exception e1) {}
		}*/
	}
	
	public static void error(String msg) {
		//try { throw new RuntimeException(msg); } catch(Exception e) { error(e); }
		error(new RuntimeException(msg));
	}
	public static void error(Exception e) {
		LogHelper loger = current();
		if (loger != null) {
			loger.lastError = e;
		}
		log("error", StringUtil.getTrace(e));
	}
	
	public static void warn(String msg) {
		log("warn", msg);
	}
	public static void warn(Exception e) {
		log("warn", StringUtil.getTrace(e));
	}
	
	public static void info(String msg) {
		log("info", msg);
	}
	public static void info(Exception e) {
		log("info", StringUtil.getTrace(e));
	}
	
	public static void debug(String msg) {
		log("debug", msg);
	}
	public static void debug(Exception e) {
		log("debug", StringUtil.getTrace(e));
	}

	public static void trace(String msg) {
		log("trace", msg);
	}
	public static void trace(Exception e) {
		log("trace", StringUtil.getTrace(e));
	}
	
	public static void end() {
		end(true, null, 0);
	}
	
	public static void end(Consumer<LogHelper> endfunc) {
		end(true, endfunc, 0);
	}
	
	public static void end(boolean keep, Consumer<LogHelper> endfunc, int funcno) {
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) return;
		
		//记录结束log
		int sz = logerlst.size();
		LogHelper loger = logerlst.get(sz-1);
		long t = System.currentTimeMillis();
		StringBuilder sb = new StringBuilder("end at ")
			.append(t).append(" cost ").append(t - loger.time).append("ms");
		loger.infolst.add(new String[]{"*sys*", buildMessage(sb.toString())});

		//选择当前结束处理函数，默认用参数指定结束处理函数
		//但若满足以下条件，则用记录结束处理函数替代参数指定结束处理函数作为当前结束处理函数
		//1、记录结束处理函数(begin设置的或子级日志传递上来的)不为null
		//2、且参数指定结束处理函数为null或其优先级低于记录结束处理函数
		if (loger.endfunc != null && (null == endfunc || funcno > loger.endfuncno)) {
			funcno = loger.endfuncno;
			endfunc = loger.endfunc;
		}

		//返写到上级log
		if (keep && sz>1) {
			LogHelper ploger = logerlst.get(sz-2);
			ploger.infolst.add(new String[]{"*sys*", outputString(null)});
			ploger.lastError = loger.lastError; //向上级日志传递错误信息
			//向上级日志传递结束处理函数(用当前结束处理函数替换上级结束处理函数)，判断条件
			//1、当前结束处理函数不为null
			//2、且上级结束处理函数(ploger.endfunc)为null或其优先级(ploger.endfuncno)低于当前结束处理函数
			if (endfunc != null && (null == ploger.endfunc || ploger.endfuncno > funcno)) {
				ploger.endfuncno = funcno; //函数覆盖
				ploger.endfunc = endfunc;  //优先级数覆盖
				endfunc = null; //当前级别不处理
			}
		}

		/* 不检查begin和end的匹配性，不限制begin和end必须写在一个函数中
		StackTraceElement pos = Thread.currentThread().getStackTrace()[2];
		if (pos.getMethodName().equals(loger.begin.getMethodName())) {*/
			if (null == endfunc) {
				logger.info(outputString(null));
			} else {
				try {
					endfunc.accept(loger);
				} catch (Exception e) {
					error(e);
				}
			}
			logerlst.remove(sz-1);
			if (logerlst.size()<=0) {
				_log.remove();
			}
		/*} else {
			System.out.println(pos.toString()+" begin-end不匹配");
		}*/
	}

	public static String outputString(StringBuilder sb) {
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) return null;

		StringBuilder tmp1 = new StringBuilder();
		StringBuilder tmp2 = new StringBuilder();
		LogHelper loger = logerlst.get(logerlst.size()-1);
		for (int i=1; i<loger.infolst.size()-1; ++i) {
			String logtype = loger.infolst.get(i)[0];
			if ("error".equals(logtype)) {
				tmp1.append('[').append(logtype).append(']');
			}
			tmp1.append(loger.infolst.get(i)[1]);
		}
		if (tmp1.length()>0 && '\n' == tmp1.charAt(tmp1.length()-1)) {
			tmp1.delete(tmp1.length()-1, tmp1.length());
		}
		tmp2.append('+').append(loger.infolst.get(0)[1])
			.append('|').append(tmp1.toString().replace("\n", "\n|").toString()).append('\n')
			.append('+').append(loger.infolst.get(loger.infolst.size()-1)[1]);
		if (sb != null) {
			sb.append(tmp2);
		}
		return tmp2.toString();
	}

	/*public static void outputPath(String path, Object... vars) {
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) return;

		if (null == path) {
			path = "";
		} else if (!path.endsWith(File.separator)) {
			path = path+File.separator;
		}
		LogHelper loger = logerlst.get(logerlst.size()-1);
		String fullpath, loginfo = LogHelper.outputString(null);
		if (loger.id instanceof String[]) {
			String[] out = (String[])loger.id;
			for (int i=0;i<out.length;++i) {
				fullpath = String.format(path+out[i], vars);
				outputFile(fullpath, loginfo);
			}
		} else {
			fullpath = String.format(path+loger.id);
			outputFile(fullpath, loginfo);
		}
	}*/
	
	public static void outputFile(String path) {
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) return;

		String loginfo = LogHelper.outputString(null);
		outputFile(path, loginfo);
	}
	
	private static void outputFile(String path, String loginfo) {
		//LogHelper loger = logerlst.get(logerlst.size()-1);
		//try {
			/*FileWriter fw = new FileWriter(path+loger.id+".log", true);
			for (int i=0; i<loger.infolst.size(); ++i) {
				fw.write(loger.infolst.get(i)[1]);
			}
			fw.close();*/
			File fp = new File(path);
			/* 建立多级目录
			File dir = fp.getParentFile();
			if (dir != null) {
				dir.mkdirs();
			}*/
			FileUtil.ansyncWriteStringToFile(fp, loginfo, "UTF-8", true);
			//FileUtil.writeStringToFile(fp, loginfo, "UTF-8", true);
		//} catch (Exception e) {
		//	e.printStackTrace();
		//}
	}
	
	public static String getLastError() {
		Exception e = getLastError(false);
		if (null == e) return null;
		return StringUtil.getTrace(e);
	}

	public static Exception getLastError(boolean clear) {
		List<LogHelper> logerlst = _log.get();
		if (null == logerlst || logerlst.size()<=0) return null;

		LogHelper loger = logerlst.get(logerlst.size()-1);
		/*for (int i=loger.infolst.size()-1; i>=0; --i) {
			if ("error".equals(loger.infolst.get(i)[0])) {
				return loger.infolst.get(i)[1];
			}
		}*/
		Exception e = loger.lastError;
		if (clear) {
			loger.lastError = null;
		}
		return e;
	}

	public static LogHelper current() {
		List<LogHelper> logerlst = _log.get();
		return (null == logerlst || logerlst.size()<=0) ?
			new LogHelper(null, 0) : logerlst.get(logerlst.size()-1);
	}

	public Object getRet() {
		return ret;
	}

	public void setRet(Object ret) {
		this.ret = ret;
	}
}
